/*
	Class: Styles
	
		Styles is a class to handle the special attribute "style." An instance
		of Styles is either a member variable of an Attributes instance or of
		the StylesManager. In the latter case, it is used to hold default styles
		and their values.
		
		The Styles class implements the concept of cascading styles. It is also
		reponsible for parsing style values (style-value pairs delimited by
		colons and semi-colons) and for converting values from string into more
		appropriate ActionScript types.
		
		If a Styles instance is a member of an Attributes instance then it is
		an event listener on the Styles instances from which it derived its
		values. that is, it is a listener on those Styles instances created
		during the parsing of a stylesheet. And if the instance was created via
		the parsing of a stylesheet, then obviously it is an event dispatcher.
*/
package com.frontalcode 
{
	import flash.events.*;
	import fl.transitions.*;
	import fl.transitions.easing.*;
	import fl.motion.Color;
	import flash.utils.Dictionary;
	
	/**                  
     * The Styles class manages a Frontal element's "style" attribute.
     */
    public class Styles extends OptionableObject
	{
		public var manager : StylesManager;
		public var attributes : Attributes;

		/*
			Variable: stylesCache
			
				Keep a cache of the style queried. This is because getStyle is
				called so often and needs to be as fast as possible.
		*/
		/**            
		 * @private
		 */
		internal var stylesCache : Object = { };		
		
		/*
			Variable: cascadingStyles
			
				An object containing a cache of the styles of the parent 
				container. This avoids having to recurse to get the parent's 
				styles.
		*/
		/**            
		 * @private
		 */
		internal var cascadingStyles : Object = { }; 

		/*
			Variable: cascadingStylesChanges
			
				Track any changes to our cascading styles. We will listen to our 
				container for its render event to clear this change log.
				
				The idea here is that style changes may have varying impacts
				on performance. So have a log of changes since the last render
				gives us an opportunity to optimize these impacts. We can take
				advantage of this by assigning categories to the different 
				styles like "textLayout" and then use styleCategoryHasChanges
				to see if any changes have occurred in this category. The result
				can tell us if we need to reformat the text tag for example.
		*/
		/**            
		 * @private
		 */
		internal var cascadingStylesChanges : Object = { };
		
		/*
			Variable: childrenCascadingStylesCacheValid
			
				As we update the cascading styles cache, we tell our children to 
				do so as well. If we need to parse our styles when it comes time
				to tell our children to re-cache, then we wait until after we
				parse to do so. This flag helps us do that.
		*/
		/**            
		 * @private
		 */
		internal var childrenCascadingStylesCacheValid : Boolean = true;
		
		/*
			Variable: styles
			
				A container's explicitly set and default styles.
		*/
		/**            
		 * @private
		 */
		internal var styles : Object = { };

		/*
			Variable: stylesChanges
			
				Track any changes to our styles. We will listen to our 
				container for its render event to clear this change log.
				
				The idea here is that style changes may have varying impacts
				on performance. So have a log of changes since the last render
				gives us an opportunity to optimize these impacts. We can take
				advantage of this by assigning categories to the different 
				styles like "textLayout" and then use styleCategoryHasChanges
				to see if any changes have occurred in this category. The result
				can tell us if we need to reformat the text tag for example.
		*/
		/**            
		 * @private
		 */
		internal var stylesChanges : Object = { };
		
		/*
			Variables: tween and progress
			
				We use this Tween to switch styles in an animated way.
				
			Notes:
			
				progress must be public so that the Tween can access it.
		*/
		/**            
		 * @private
		 */
		internal var tween : Tween;
		
		/**            
		 * @private
		 */
		public var progress : Number = 0;

		/*
			Constructor:
			
				Populate our cascading styles as an amalgam of our container's
				parent's cascading styles and explicit styles..
				
				Parse the container's style and class attributes.
				
			Parameters:
			
				manager		- the document-specific style manager
				attributes	- our associated Attributes instance (null for a set of default styles)
		*/	
		/**
		 * Creates a new Styles instance.
		 */
		public function Styles ( manager : StylesManager, attributes : Attributes = null, options : Object = null )
		{
			super ( options );
			
			this.manager = manager;
			this.attributes = attributes;
			
			if ( container != null ) 
			{
				cacheCascadingStyles ( );
				parseStyleString ( attributes.getAttribute ( "style" ), true );
				parseStyles ( );
			}
		}
		
		public function get container ( ) : DocumentElement { return attributes == null ? null : attributes.container; }
				
		public function getStyle ( style : String, force : String = null ) : *
		{
			if ( stylesCache.hasOwnProperty ( style ) ) return stylesCache [ style ];
		
			var result : *;
			var styleDefn : Object = manager.getStyleDefn ( style );
			
			if ( force == "local" ||
				( styles [ style ] != null && styles [ style ].value !== undefined && ( styleDefn.options.uninheritable || styles [ style ].value != "inherit" ) ) )
			{
				// The style is explicitly set or we're forcing a local query.
				//
				result = styles [ style ] != null ? styles [ style ].value : undefined;
				stylesCache [ style ] = result;
				return result;
			}
			
			if ( force != "cascade" && ! styleDefn.options.cascading )
			{
				// The style is a local style and we're not forcing a cascading 
				// query.
				//
				addListener ( manager.defaultStyles, FrontalEvent.CHANGE, parseStyles, false, 0, true );
				result = manager.defaultStyles.getStyle ( style, "local" );
				stylesCache [ style ] = result;			
				return result;
			}
			
			if ( force == "cascade" || styleDefn.options.cascading )
			{
				// Look for it in a cascading way.
				//
				if ( cascadingStyles [ style ] != null && 
					( styleDefn.options.uninheritable || cascadingStyles [ style ].value != "inherit" ) ) 
				{
					result = cascadingStyles [ style ].value;
					stylesCache [ style ] = result;
					return result;
				}
				addListener ( manager.defaultStyles, FrontalEvent.CHANGE, parseStyles, false, 0, true );
				result = manager.defaultStyles.getStyle ( style, "local" );
				stylesCache [ style ] = result;
				return result;
			}
			
			stylesCache [ style ] = undefined;
			return undefined;
		}
		
		/*
			Function: setStyle
			
				Set a style. By specifying a value of undefined, a style can be
				removed.
				
			Parameters:
			
				name				- the name of the attribute
				value				- the converted value, use undefined to remove an attribute
				rawValue			- is this an unconverted value?
				doesNotNeedRender	- do not mark our container as needing rendering
				isOriginal			- is this from the initial parsing of the node?
				
			See Also:
			
				<convertValue>
		*/	
		public function setStyle ( style : String, value : *, rawValue : Boolean = false, doesNotNeedRender : Boolean = false, isOriginal : Boolean = false, dispatchChangeEvent : Boolean = true ) : void
		{
			var valueChanged : Boolean = false;
			var needsLayout : Boolean = false;
			var needsParse : Boolean = false;
			var isCascading : Boolean = false;
			var results : Array = createStyleEntry ( style, value, rawValue );
			for ( var i : int = 0; i < results.length; i++ )
			{
				var name : String = results [ i ].styleDefn.style;
				var value : * = results [ i ].value;
				if ( styles [ name ] == null ) styles [ name ] = { styleDefn: results [ i ].styleDefn, categories: { } };
				styles [ name ].touched = true;
				if ( styles [ name ].value != value )
				{
					valueChanged = true;
					for each ( var category : String in results [ i ].styleDefn.options.categories )
					{
						styles [ name ].categories [ category ] = true;
						if ( category == "layout" ) needsLayout = true;
					}
					styles [ name ].value = value;
					stylesChanges [ name ] = styles [ name ];
				}
				
				if ( isOriginal )
				{
					if ( value === undefined )
					{
						delete styles [ name ].isOriginal;
						delete styles [ name ].originalValue;
					}
					else
					{
						styles [ name ].isOriginal = true;
						styles [ name ].originalValue = value;
					}
				}
				
				if ( value === undefined )
				{
					delete stylesCache [ name ];
					needsParse = true;
				}
				else
				{
					stylesCache [ name ] = value;
				}
				
				if ( results [ i ].styleDefn.options.cascading ) isCascading = true;
				
				// XXX We do not yet update the XML node's style attribute. See
				// toString ( ) for a discussion.
				//
				// if ( attributes != null ) attributes.setAttribute ( "style", toString ( ), doesNotNeedRender );
			}
			
			// If we have a container, let it know it might need to be rendered
			// after the style changes.
			//
			if ( container != null && ! doesNotNeedRender && valueChanged )
			{
				container.needsRender = true;
				if ( needsLayout && container is Container ) ( container as Container ).needsLayout = true;
				if ( needsParse ) container.needsParse = true;
				if ( dispatchChangeEvent && isCascading ) dispatchEvent ( new FrontalEvent ( FrontalEvent.CHANGE ) );
			}
		}
		
		/**            
		 * @private
		 */
		internal function createStyleEntry ( style : String, value : *, rawValue : Boolean = false ) : Array
		{
			var styleDefn : Object = manager.getStyleDefn ( style );
			
			// An undefined value means we are removing this style so do not 
			// validate the value.
			//
			if ( value === undefined || value === "undefined" || value === "" )
			{
				value = undefined;
				rawValue = false;
			}
			
			if ( rawValue && styleDefn.validator != null )
			{
				try
				{
					if ( 
							( styleDefn.validator is RegExp && ! styleDefn.validator.test ( value.toString ( ) ) ) ||
							( styleDefn.validator is Function && ! styleDefn.validator ( styleDefn, value.toString ( ) ) )
						)
					{
						Debugger.logMessage ( Debugger.INFO, "Styles", "illegal value for style " + style + ": " + value );
					}
				}
				catch ( e : Error )
				{
					Debugger.logMessage ( Debugger.INFO, "Styles", "illegal value for style " + style + ": " + value );
				}
			}
			
			var result : Array;
			
			if ( styleDefn.options.processor )
			{
				result = styleDefn.options.processor ( styleDefn, value, rawValue );
			}
			else
			{
				result = [ { styleDefn: styleDefn, value: rawValue ? convertValue ( styleDefn, value ) : value } ];
			}
			
			return result;
		}
		
		/*
			XXX Not yet implemented because of a few things. First, we don't
			have an "unconvertValue" method to go from, say, a string to a
			quoted string or from a number like "50" back to its original value
			like "50px." (Maybe we don't need to?) Also, if our styles were
			generated from a composite style like "margin," is it okay to 
			leave that broken up? I think so.
		internal function toString ( ) : String
		{
			var result : String = null;
			
			for each ( var style : Object in styles )
			{
				// We don't use getStyle as that gets cascading values and such
				// and we're interested in building the actual value for the
				// XML node's style attribute.
				//
				if ( style.value != undefined )
				{
					if ( result == null ) result = "";
					result += style + ": " + unconvertValue ( value );
				}
			}
		}
		*/
		
		/*
			Function: parseStyles
			
				(Re)build our styles. The process clears our current styles
				first.
		*/
		/**            
		 * @private
		 */
		internal function parseStyles ( event : FrontalEvent = null ) : void
		{
			// XXX Consider the situation that we're responding to an event that
			// one of our base styles has changed. We want to stamp out the
			// event if possible (which means implementing some sort of style
			// diff). But if we can't and we do change our styles then we need
			// to dispatch an event about that.
			//
			
			if ( container != null )
			{
				stylesCache = { };
				
				// Save the current value of style-remove-tween-duration. This
				// style works in a reverse fashion in that it gets applied when
				// it gets removed rather than when it is applied. So if after
				// we apply all of our new styles, this style is no longer set
				// then we use it for our style tween duration. As a result, it
				// cannot be applied universally as then it is never unset. 
				//
				var duration : Number = Number ( getStyle ( "style-remove-tween-duration" ) );
				
				// Apply any stylesheet rule sets. They are sorted based on 
				// their specificity and creation order. Styles assigned via the
				// style attribute (our "original" styles) always come first as 
				// described here:
				//
				// http://www.w3.org/TR/CSS21/cascade.html#specificity
				//
				var newStyles : Object = { };
				for each ( var style : Object in styles ) 
				{
					if ( style.isOriginal && ! newStyles.hasOwnProperty ( style.styleDefn.style ) ) newStyles [ style.styleDefn.style ] = style.originalValue;
				}
				
				attributes.sortSelectors ( );
				
				for each ( var selector : StylesheetSelector in attributes.selectors ) 
				{
					// Only apply the declarations of those selectors whose 
					// constraints are currently matched. 
					//
					if ( attributes.selectorConstraints [ selector ].match )
					{
						for ( var styleName : String in selector.attributes.styles.styles )
						{
							var results : Array = createStyleEntry ( styleName, selector.attributes.styles.styles [ styleName ].value );
							for ( var i : int = 0; i < results.length; i++ )
							{
								if ( ! newStyles.hasOwnProperty ( results [ i ].styleDefn.style ) ) newStyles [ results [ i ].styleDefn.style ] = results [ i ].value;
							}
						}
						
						addListener ( selector.attributes.styles, FrontalEvent.CHANGE, parseStyles, false, 0, true );
					}
				}
				
				// Mark as untouched our current styles and track their current
				// values. We'll use these values after we re-generate our 
				// styles to setup our style tween.
				//
				untouchStyles ( );
				for ( styleName in newStyles ) setStyle ( styleName, newStyles [ styleName ], false, false, false, false );
				deleteUntouchedStyles ( );
				
				tweenStyleChanges ( duration );
				
				for each ( style in styles )
				{
					if ( style.value != style.lastValue )
					{
						dispatchEvent ( new FrontalEvent ( FrontalEvent.CHANGE ) );
						break;
					}
				}
			}
			
			cacheChildrenCascadingStyles ( );
		}
			
		public function fforwardStyleTween ( ) : void
		{
			if ( tween != null && tween.isPlaying )
			{
				tween.stop ( );
				setTweeningStyles ( 1 );
			}
		}
		
		/*
			Function: stopStyleTween
			
				Be careful using this as it will not set the styles back to 
				their original values like, say, "100%."
		*/
		public function stopStyleTween ( ) : void
		{
			if ( tween != null && tween.isPlaying ) tween.stop ( );
		}
		
		public function tweenStyleChanges ( duration : Number = NaN ) : void
		{
			if ( DocumentManager.getInstance ( ).getObjectOption ( "isStyleInliner" ) ) return;
			
			// Have we changed anything? Should we tween the style changes?
			//
			var hasTweenableChanges : Boolean = false;
			var hasNewTweenableChanges : Boolean = false;
			duration = ! Util.isNumber ( duration ) ? Number ( getStyle ( "style-tween-duration" ) ) : duration;
			if ( ! Util.isNumber ( duration ) ) duration = 0;
			var useSeconds : Boolean = getStyle ( "style-tween-use-secs" );
			
			var styleTweenDetails : Dictionary = new Dictionary ( );
			
			for each ( var style : Object in styles )
			{
				var targetValue  : * = style.value;
				var lastValue    : * = style.lastValue;
				var defaultValue : * = style.styleDefn.options.hasOwnProperty ( "defaultForTween" ) ? style.styleDefn.options.defaultForTween : style.styleDefn.defaultValue;
					
				if ( style.styleDefn.options.dimensionStyle != null && container != null ) 
				{
					// We do the following calculations in try statements as
					// there is a possibility that we do not yet have enough
					// info to complete the calculations in which case we 
					// cannot do the tween.
					//
					if ( ! Util.isNumber ( targetValue ) )
					{
						try
						{
							targetValue = ( container as Container ).evaluateDimensionStyle ( targetValue, style.styleDefn.options.dimensionStyle == "width", targetValue );
						}
						catch ( e : * ) { }
					}
					
					if ( ! Util.isNumber ( lastValue ) )
					{
						try
						{
							lastValue = ( container as Container ).evaluateDimensionStyle ( lastValue, style.styleDefn.options.dimensionStyle == "width", lastValue );
						}
						catch ( e : * ) { }
					}
				}
				
				if ( ! Util.isNumber ( targetValue ) ) targetValue = defaultValue;
				
				if ( attributes.initialStylesApplied && ! Util.isNumber ( lastValue ) ) 
				{
					// Get the default value for the style.
					//
					lastValue = defaultValue;
					if ( container != null ) lastValue = container.getMappedStyleValue ( style.styleDefn.style );
				}
				
				// It's tweenable if it has a numeric start value 
				// and a numeric last value.
				//
				if ( 
						! style.styleDefn.options.doNotTween && 
						Util.isNumber ( lastValue ) && 
						Util.isNumber ( targetValue )
					)
				{
					// XXX With the movie-registration-* styles, I've seen miniscule
					// differences that should not be tweened. This rounding error
					// should be fixed somewhere... FrontalSprite?
					//
					if ( targetValue != lastValue && Math.abs ( targetValue - lastValue ) > 0.00001 )
					{
						hasTweenableChanges = true;
						if ( ! hasNewTweenableChanges ) hasNewTweenableChanges = style.targetValue != targetValue;
						styleTweenDetails [ style ] = {
								lastValue:			lastValue,
								targetValue:		targetValue,
								targetStyleValue:	style.value
							};
					}
					else
					{
						style.targetStyleValue = style.targetValue = targetValue;
					}
				}
			}
			
			if ( tween != null && tween.isPlaying )
			{
				if ( ! hasTweenableChanges && tween.position == 0 )
				{
					// If all of our targets match all of our last values and
					// the tween hasn't progressed then we can stop it.
					//
					tween.stop ( );
					return;
				}

				if ( ! hasNewTweenableChanges ) 
				{
					// Just let the tween continue as there are no changes.
					//
					return;
				}
			}

			if ( hasTweenableChanges )
			{
				if ( tween == null )
				{
					tween = new Tween ( this, "progress", null, 0, 1, 1, false );
					addListener ( tween, TweenEvent.MOTION_CHANGE, tweenHandler );
					addListener ( tween, TweenEvent.MOTION_FINISH, tweenHandler );
				}
				else if ( tween.isPlaying )
				{
					// The tween is already in progress. In this case, we
					// subtract the time remaining from the new duration.
					// 
					
					var timeRemaining : Number = tween.duration - tween.time;
					
					if ( useSeconds )
					{
						if ( tween.useSeconds )
						{
							duration -= timeRemaining;
						}
						else
						{
							duration -= timeRemaining * DocumentManager.getInstance ( ).fps;
						}
					}
					else
					{
						if ( tween.useSeconds )
						{
							duration -= timeRemaining / DocumentManager.getInstance ( ).fps;
						}
						else
						{
							duration -= timeRemaining;
						}
					}
					
					if ( duration < 0 ) duration = 0;
					
					tween.stop ( );
				}
				
				if ( duration > 0 )
				{
					for each ( style in styles )
					{
						style.tweenable = false;
				
						if ( styleTweenDetails [ style ] != null )
						{
							// Set the style back to lastValue and set our
							// targetValue to the current value.
							//
							style.tweenable = true;
							style.lastValue = styleTweenDetails [ style ].lastValue;
							style.targetValue = styleTweenDetails [ style ].targetValue;
							style.targetStyleValue = styleTweenDetails [ style ].targetStyleValue;
							setStyle ( style.styleDefn.style, style.lastValue );
						}
					}
					
					tween.func = getStyle ( "style-tween-ease" );
					tween.duration = duration;
					tween.useSeconds = useSeconds;
					tween.start ( );
				}
			}
			else
			{
				if ( tween != null ) tween.stop ( );
			}
		}
		
		/**            
		 * @private
		 */
		public function tweenHandler ( event : TweenEvent ) : void
		{
			setTweeningStyles ( progress );
			
			if ( event.type == TweenEvent.MOTION_FINISH )
			{
				for each ( var style : Object in styles )
				{
					if ( style.deleteAfterTween )
					{
						setStyle ( style.styleDefn.style, undefined );
						if ( ! style.isOriginal ) delete styles [ style ];
					}
				}
			}
		}
		
		/**            
		 * @private
		 */
		internal function setTweeningStyles ( progress : Number ) : void
		{
			for each ( var style : Object in styles )
			{
				if ( style.tweenable )
				{
					var value : *;
					
					if ( progress >= 1 ) 
					{
						// We've been tweening on the numeric conversion of the
						// style's value. Now go back to the original which may
						// be, say, "100%."
						//
						value = style.targetStyleValue;
					}
					else
					{
						if ( style.styleDefn.options.isColor )
						{
							value = Color.interpolateColor ( style.lastValue, style.targetValue, progress );
						}
						else
						{
							// Update our target value since if it's percentage
							// based it may have changed.
							//
							if ( style.styleDefn.options.dimensionStyle != null && container != null ) 
							{
								try
								{
									var targetValue : Number = ( container as Container ).evaluateDimensionStyle ( style.targetStyleValue, style.styleDefn.options.dimensionStyle == "width", style.targetStyleValue );
									style.targetValue = targetValue;
								}
								catch ( e : * ) { }
							}
							
							value = style.lastValue + ( style.targetValue - style.lastValue ) * progress;
						}
					}
 
					setStyle ( style.styleDefn.style, value );
				}
			}
		}
		
		/*
			Function: cacheCascadingStyles
			
				This is an optimization in which we copy our parent's cascading 
				style cache and its styles into our own cascading style cache.
				This makes looking up cascading values very quick.
				
				This function is also a callback in the event of a change in our
				parent's styles.
				
			Parameters:
			
				event		- The change event if called as an event handler 
							  otherwise null.
				
			Note:
			
				This function should only be called if this Styles instance is
				associated with a container.
				
				XXX Does this ever get cleared or do we just keep stacking up
				styles in it?
		*/
		private function cacheCascadingStyles ( ) : void
		{
			if ( container.parent == null ) return;
			
			stylesCache = { }; 
			
			var parentStyles : Styles = container.parent.attributes.styles;
			var changed : Boolean = false;
			
			for ( var style : String in parentStyles.cascadingStyles )
			{
				// Only set those styles that are not in the parent styles since
				// they'll be overwritten anyway.
				//
				if ( ( ! parentStyles.styles.hasOwnProperty ( style ) || parentStyles.styles [ style ].value == undefined ) && setCascadingStyle ( style, parentStyles.cascadingStyles [ style ].value ) ) changed = true;
			}
			
			for ( style in parentStyles.styles )
			{
				if ( parentStyles.styles [ style ].styleDefn.options.cascading && parentStyles.styles [ style ].value != undefined && setCascadingStyle ( style, parentStyles.styles [ style ].value ) ) changed = true;
			}
			
			addListener ( parentStyles, FrontalEvent.CHANGE, cascadingStyleChangeHandler, false, 0, true );
			
			if ( changed )
			{
				container.needsRender = true;
				
				// Tell our container's children to cache their cascading 
				// styles unless we need to parse in which case it will
				// happen then.
				//
				childrenCascadingStylesCacheValid = false;
				if ( ! container.needsParse ) cacheChildrenCascadingStyles ( );
			}
		}
		
		private function cascadingStyleChangeHandler ( event : FrontalEvent = null ) : void
		{
			cacheCascadingStyles ( );
		}
		
		private function setCascadingStyle ( style : String, value : * ) : Boolean
		{
			var changed : Boolean = false;
 
			if ( ! cascadingStyles.hasOwnProperty ( style ) || cascadingStyles [ style ].value != value )
			{
				changed = true;
				var entry : Object = cascadingStyles.hasOwnProperty ( style ) ? cascadingStyles [ style ] : { styleDefn: manager.getStyleDefn ( style ), categories: { } };
				entry.value = value;
				for each ( var category : String in entry.styleDefn.options.categories ) entry.categories [ category ] = true;
				cascadingStylesChanges [ style ] = cascadingStyles [ style ] = entry;
			}
			
			return changed;
		}
		
		private function cacheChildrenCascadingStyles ( ) : void
		{
			if ( ! childrenCascadingStylesCacheValid )
			{
				childrenCascadingStylesCacheValid = true;
				for ( var i : uint = 0; i < container.elements.length; i++ ) container.elements [ i ].attributes.styles.cacheCascadingStyles ( );
			}
		}
		
		/*
			Function: parseStyleString
			
				Parse a string of styles into our list of styles. This process
				adds to / overwrites our current styles.
		*/
		/**            
		 * @private
		 */
		internal function parseStyleString ( stylesString : String, isOriginal : Boolean = false ) : void
		{
			if ( stylesString != null )
			{
				var stylePairs : Array = stylesString.split ( /\s*;\s*/ );
				
				for ( var i : int = 0; i < stylePairs.length; i++ )
				{
					if ( stylePairs [ i ].length == 0 ) continue;
					
					var firstColon : Number = stylePairs [ i ].indexOf ( ":" );
					var pieces : Array = [ stylePairs [ i ].substring ( 0, firstColon ), stylePairs [ i ].substring ( firstColon + 1 ) ];
					
					pieces [ 0 ] = Util.trim ( pieces [ 0 ] );
					pieces [ 1 ] = Util.trim ( pieces [ 1 ] );
					
					var style : String	= pieces [ 0 ].toLowerCase ( );
					var value : String	= pieces [ 1 ];
					
					if ( style && style.length )
					{
						setStyle ( style, value, true, false, isOriginal );
					}
				}
			}
		}
		
		/*
			Function: untouchStyles
			
				We un-touch all of our styles. This is in preparation for making
				mass changes during which we'll touch each style we set. After 
				making our changes, we'll remove any styles that were not 
				touched.
				
				At this point, we also track the current value of each style.
				This is to aid in tweening from one set of styles to another.
		*/
		/**            
		 * @private
		 */
		internal function untouchStyles ( ) : void
		{
			for each ( var style : Object in styles ) 
			{
				style.lastValue = style.value;
				style.touched = false;
				style.deleteAfterTween = false;
			}
		}
		
		/*
			Function: deleteUntouchedStyles
			
				We have completed a mass change of styles and now want to remove
				any styles that were not set.
		*/
		/**            
		 * @private
		 */
		internal function deleteUntouchedStyles ( ) : void
		{
			for each ( var style : Object in styles ) 
			{
				if ( ! style.touched ) 
				{
					if ( style.tweenable && tween != null && tween.isPlaying )
					{
						style.deleteAfterTween = true;
					}
					else
					{
						setStyle ( style.styleDefn.style, undefined );
						if ( ! style.isOriginal ) delete styles [ style ];
					}
				}
			}
		}
		
		/**            
		 * @private
		 */
		static internal function convertValue ( styleDefn : Object, value : String ) : *
		{
			var result : *;
			
			if ( value == "null" )
			{
				result = null;
			}
			else if ( value == "undefined" )
			{
				result = undefined;
			}
			else if ( ( value.substr ( 0, 1 ) == "'" && value.substr ( -1 ) == "'" ) || ( value.substr ( 0, 1 ) == '"' && value.substr ( -1 ) == '"' )  )
			{
				result = value.substr ( 1, value.length - 2 );
				result = value.replace ( /\\'/, "'" );
				result = value.replace ( /\\"/, "\"" );
			}
			else if ( Util.isTrue.test ( value ) )
			{
				result = true;
			}
			else if ( Util.isFalse.test ( value ) )
			{
				result = false;
			}
			else if ( styleDefn.options.isColor && value.charAt ( 0 ) == "#" )
			{
				// Assume it's a color.
				//
	
				result = Number ( '0x' + value.substr ( 1, 6 ) );
			}
			else if ( ( styleDefn.validator === Util.lengthSpec || styleDefn.validator === Util.decimalPx || styleDefn.validator === Util.decimalPxOrPercent ) && new RegExp ( "^-?[0-9.]+px$" ).test ( value ) )
			{
				// Assume it's a pixel value.
				//
				
				result = Number ( value.substr ( 0, value.length - 2 ) );
			}
			else if ( Util.isNumber ( Number ( value ) ) )
			{
				result = Number ( value );
			}
			else
			{
				result = value;
			}
			
			return result;
		}
		
		/**            
		 * @private
		 */
		static internal function unconvertValue ( styleDefn : Object, value : * ) : *
		{
			var result : * = value;
			
			if ( styleDefn.options.isColor && Util.isNumber ( value ) )
			{
				result = Number ( value ).toString ( 16 );
				result = "#" + "000000".substr ( 0, 6 - result.length ) + result;
			}
			else if ( ( styleDefn.validator === Util.lengthSpec || styleDefn.validator === Util.decimalPx || styleDefn.validator === Util.decimalPxOrPercent ) && Util.isNumber ( value ) )
			{
				// Assume it's a pixel value.
				//
				
				result = value + "px";
			}
			
			return result;
		}
		
		/*
			Function: clearChanges
			
				Called from our Attributes instance upon hearing our container's
				render event.
		*/
		/**            
		 * @private
		 */
		internal function clearChanges ( category : String = null ) : void
		{
			if ( category == null )
			{
				cascadingStylesChanges = { };
				stylesChanges = { };
			}
			else
			{
				for each ( var style: Object in stylesChanges )
				{
					delete style.categories [ category ];
				}
				
				for each ( style in cascadingStylesChanges )
				{
					delete style.categories [ category ];
				}
			}
		}
		
		public function styleCategoryHasChanges ( category : String ) : Boolean
		{
			var result : Boolean = false;
			
			for each ( var style: Object in stylesChanges )
			{
				if ( style.categories [ category ] )
				{
					result = true;
					break;
				}
			}
			
			if ( ! result )
			{
				for each ( style in cascadingStylesChanges )
				{
					if ( style.categories [ category ] )
					{
						result = true;
						break;
					}
				}
			}
			
			return result;
		}
    }
}

